// WavTest.cpp: определяет точку входа для консольного приложения.
//
#define BLOCKSIZE 512
#define PI 3.1415
#include "wav.h"
#include <iostream>
#include<cstdio>
#include <cmath>
void err(cudaError_t e)
{
	if (e != cudaSuccess)
		throw e;
}
__global__ void TD_OLA_0_75_GPU(unsigned char * inData, unsigned char * outData)
{	
	__shared__ unsigned char blockData [BLOCKSIZE];
	float scale = 0.75;
	int tid = threadIdx.x;
	int bid = blockIdx.x;
	blockData[tid] = inData[tid + bid * BLOCKSIZE];
	__syncthreads();
	if (tid < BLOCKSIZE * scale)
		outData[tid + (long)(bid * BLOCKSIZE * scale)] =
			blockData[tid] * (1 - (float)tid / (BLOCKSIZE * scale))
			+
			blockData[tid + (long)((1 - scale) * BLOCKSIZE)] * ((float) tid / (BLOCKSIZE * scale))
			;
}

Wav* TD_OLAScale_0_75_GPU(Wav *wav)
{
	try
	{
		float scale = 0.75;
		Wav *newWav = new Wav();
		WavHeader header = wav->getHeader();
		newWav->setLength(scale * wav->getLength() + 1);
		header.subchunk2Size = newWav->getLength();
		header.chunkSize = 20 + header.subchunk1Size + header.subchunk2Size;
		unsigned char *hostOutData = new unsigned char[newWav->getLength()];
		unsigned char *deviceOutData;
		unsigned char *deviceInData;
		long numBlocks = wav->getLength() / BLOCKSIZE;
		
		err( cudaMalloc ((void**) & deviceInData, wav->getLength()));
		err( cudaMalloc ((void**) & deviceOutData, newWav->getLength()));
		err( cudaMemcpy (deviceInData, wav->getData(), wav->getLength(), cudaMemcpyHostToDevice));
		TD_OLA_0_75_GPU<<<dim3(numBlocks),dim3(BLOCKSIZE)>>>(deviceInData,deviceOutData);
		err( cudaMemcpy (hostOutData, deviceOutData, newWav->getLength(), cudaMemcpyDeviceToHost));



		newWav->setHeader(header);
		newWav->setData(hostOutData);
		return newWav;
	}
	catch(...)
	{
		std::cout<<"td-ola failed";
		return NULL;
	}
}

Wav* TD_OLAScale_0_75_CPU(Wav *wav)
{
	try
	{
		float scale = 0.75;
		Wav *newWav = new Wav();
		WavHeader header = wav->getHeader();
		newWav->setLength(scale * wav->getLength() + 1);
		header.subchunk2Size = newWav->getLength();
		header.chunkSize = 20 + header.subchunk1Size + header.subchunk2Size;
		unsigned char *data = new unsigned char[newWav->getLength()];

		long numBlocks = wav->getLength() / BLOCKSIZE;
		for(long j = 0; j < numBlocks; j ++)
		{

			//root(window appied)
			for(long i = 0; i < BLOCKSIZE * scale + 1; i ++)
			{
				data[i + (long)(j * BLOCKSIZE * scale)] =
				(

					wav->getData()[i + j * BLOCKSIZE] //down
						*  (1 - (float)i / (BLOCKSIZE * scale))
							+
					wav->getData()[i + long((1 - scale) * BLOCKSIZE  + j * BLOCKSIZE)] //up
						*  ((float)i ) / (BLOCKSIZE * scale )


				);

			}

		}



		newWav->setHeader(header);
		newWav->setData(data);
		return newWav;
	}
	catch(...)
	{
		std::cout<<"td-ola failed";
		return NULL;
	}
}
__global__ void TD_OLA_1_5_GPU(unsigned char * inData, unsigned char * outData)
{	
	__shared__ unsigned char blockData [BLOCKSIZE];
	float scale = 1.5;
	int tid = threadIdx.x;
	int bid = blockIdx.x;
	blockData[tid] = inData[tid + bid * BLOCKSIZE];
	__syncthreads();
	if (tid < BLOCKSIZE / 2)
		outData[tid + (long)(bid * BLOCKSIZE * scale)] =
			blockData[tid] ;
	else 
	{
		outData[tid + (long)(bid * BLOCKSIZE * scale) ] =
			(unsigned char)blockData[tid] * 
				(BLOCKSIZE - (float) tid) / (BLOCKSIZE / 2) 
			+
			(unsigned char)blockData[tid - BLOCKSIZE / 2] * 
				((float) tid + 1 - BLOCKSIZE / 2) / (BLOCKSIZE/2);	
			outData[(long) (tid + BLOCKSIZE / 2 + bid * BLOCKSIZE * scale) ] =
				blockData[tid];
	}
}
Wav* TD_OLAScale_1_5_GPU(Wav *wav)
{
	try
	{

		Wav *newWav = new Wav();
		WavHeader header = wav->getHeader();
		newWav->setLength(1.5 * wav->getLength() + 1);
		header.subchunk2Size = newWav->getLength();
		header.chunkSize = 20 + header.subchunk1Size + header.subchunk2Size;

		unsigned char *hostOutData = new unsigned char[newWav->getLength()];
		unsigned char *deviceOutData;
		unsigned char *deviceInData;
		long numBlocks = wav->getLength() / BLOCKSIZE;
		
		err( cudaMalloc ((void**) & deviceInData, wav->getLength()));
		err( cudaMalloc ((void**) & deviceOutData, newWav->getLength()));
		err( cudaMemcpy (deviceInData, wav->getData(), wav->getLength(), cudaMemcpyHostToDevice));
		TD_OLA_1_5_GPU<<<dim3(numBlocks),dim3(BLOCKSIZE)>>>(deviceInData,deviceOutData);
		err( cudaMemcpy (hostOutData, deviceOutData, newWav->getLength(), cudaMemcpyDeviceToHost));




		newWav->setHeader(header);
		newWav->setData(hostOutData);
		return newWav;
	}
	catch(...)
	{
		std::cout<<"td-ola failed";
		return NULL;
	}
}

Wav* TD_OLAScale_1_5_CPU(Wav *wav)
{
	try
	{

		Wav *newWav = new Wav();
		WavHeader header = wav->getHeader();
		newWav->setLength(1.5 * wav->getLength() + 1);
		header.subchunk2Size = newWav->getLength();
		header.chunkSize = 20 + header.subchunk1Size + header.subchunk2Size;
		unsigned char *data = new unsigned char[newWav->getLength()];
		long numBlocks = wav->getLength() / BLOCKSIZE;
		for(long j = 0; j < numBlocks; j ++)
		{
			//prefix
			for(long i = 0; i < BLOCKSIZE / 2; i ++)
				data[i + j * BLOCKSIZE * 3 / 2] = wav->getData()[i + j * BLOCKSIZE]
					;
			//root(window appied)
			for(long i = BLOCKSIZE / 2; i < BLOCKSIZE; i ++)
			{
				data[i + j * BLOCKSIZE * 3 / 2] =
				(

					wav->getData()[i + j * BLOCKSIZE] //down
						*(BLOCKSIZE - (double)i) / (BLOCKSIZE / 2)
							+
					wav->getData()[i - BLOCKSIZE / 2 + j * BLOCKSIZE] //up
						*  ((double)i + 1 - BLOCKSIZE / 2) / (BLOCKSIZE / 2)


				);
			}
			//suffix
			for(long i = 0; i < BLOCKSIZE / 2; i ++)
				data[i + BLOCKSIZE + j * BLOCKSIZE * 3 / 2] =
					wav->getData()[i + BLOCKSIZE / 2 + j * BLOCKSIZE];
		}


		newWav->setHeader(header);
		newWav->setData(data);
		return newWav;
	}
	catch(...)
	{
		std::cout<<"td-ola failed";
		return NULL;
	}
}
__global__ void interpolation_1_5_GPU(unsigned char * inData, unsigned char * outData)
{	
	__shared__ unsigned char blockData [BLOCKSIZE];
	float scale = 1.5;
	int tid = threadIdx.x;
	int bid = blockIdx.x;
	blockData[tid] = inData[tid + bid * BLOCKSIZE];
	__syncthreads();
	outData[(long)(tid * scale  + (bid * BLOCKSIZE * scale))] =
			blockData[tid];
	if (tid % 2 == 1)
	outData[(long)(tid * scale + (bid * BLOCKSIZE * scale)) + 1] = blockData[tid];
}

Wav* interpolationScale_1_5_GPU(Wav *wav)
{
		float scale = 1.5;
		Wav *newWav = new Wav();
		WavHeader header = wav->getHeader();
		newWav->setLength(scale * wav->getLength() + 1);
		header.subchunk2Size = newWav->getLength();
		header.chunkSize = 20 + header.subchunk1Size + header.subchunk2Size;
		unsigned char *hostOutData = new unsigned char[newWav->getLength()];
		unsigned char *deviceOutData;
		unsigned char *deviceInData;
		long numBlocks = wav->getLength() / BLOCKSIZE;
		
		err( cudaMalloc ((void**) & deviceInData, wav->getLength()));
		err( cudaMalloc ((void**) & deviceOutData, newWav->getLength()));
		err( cudaMemcpy (deviceInData, wav->getData(), wav->getLength(), cudaMemcpyHostToDevice));
		interpolation_1_5_GPU<<<dim3(numBlocks),dim3(BLOCKSIZE)>>>(deviceInData,deviceOutData);
		err( cudaMemcpy (hostOutData, deviceOutData, newWav->getLength(), cudaMemcpyDeviceToHost));



		newWav->setHeader(header);
		newWav->setData(hostOutData);
		return newWav;
}

Wav* interpolationScale_1_5_CPU(Wav *wav)
{
	Wav *newWav = new Wav();
	WavHeader header = wav->getHeader();
	newWav->setLength(1.5 * wav->getLength() + 1);
	header.subchunk2Size = newWav->getLength();
	header.chunkSize = 20 + header.subchunk1Size + header.subchunk2Size;
	unsigned char *data = new unsigned char[newWav->getLength()];
	for(long i = 0; i < wav->getLength(); i ++)
		data[(long)(i * 1.5)] = wav->getData()[i];

	for(long i = 2; i < newWav->getLength(); i += 3)
	{
		data[i] =  data[i - 1];
	}
	newWav->setHeader(header);
	newWav->setData(data);
	return newWav;
}
__global__ void interpolation_0_75_GPU(unsigned char * inData, unsigned char * outData)
{	
	__shared__ unsigned char blockData [BLOCKSIZE];
	float scale = 0.75;
	int tid = threadIdx.x;
	int bid = blockIdx.x;
	blockData[tid] = inData[tid + bid * BLOCKSIZE];
	__syncthreads();
	outData[(long)(tid * scale  + (bid * BLOCKSIZE * scale))] =
			blockData[tid];
}

Wav* interpolationScale_0_75_GPU(Wav *wav)
{
		float scale = 0.75;
		Wav *newWav = new Wav();
		WavHeader header = wav->getHeader();
		newWav->setLength(scale * wav->getLength() + 1);
		header.subchunk2Size = newWav->getLength();
		header.chunkSize = 20 + header.subchunk1Size + header.subchunk2Size;
		unsigned char *hostOutData = new unsigned char[newWav->getLength()];
		unsigned char *deviceOutData;
		unsigned char *deviceInData;
		long numBlocks = wav->getLength() / BLOCKSIZE;
		
		err( cudaMalloc ((void**) & deviceInData, wav->getLength()));
		err( cudaMalloc ((void**) & deviceOutData, newWav->getLength()));
		err( cudaMemcpy (deviceInData, wav->getData(), wav->getLength(), cudaMemcpyHostToDevice));
		interpolation_0_75_GPU<<<dim3(numBlocks),dim3(BLOCKSIZE)>>>(deviceInData,deviceOutData);
		err( cudaMemcpy (hostOutData, deviceOutData, newWav->getLength(), cudaMemcpyDeviceToHost));



		newWav->setHeader(header);
		newWav->setData(hostOutData);
		return newWav;
}
Wav* interpolationScale_0_75_CPU(Wav *wav)
{
	Wav *newWav = new Wav();
	WavHeader header = wav->getHeader();

	newWav->setLength(0.75 * wav->getLength() + 1);
	header.subchunk2Size = newWav->getLength();
	header.chunkSize = 20 + header.subchunk1Size + newWav->getLength();
	unsigned char *data = new unsigned char[newWav->getLength()];
	for(long i = 0; i < wav->getLength(); i ++)
		data[(long)(i * 0.75)] = wav->getData()[i];


	newWav->setHeader(header);
	newWav->setData(data);
	return newWav;
}
Wav* getConstantSound(Wav *wav)
{
	Wav *newWav = new Wav();
	WavHeader header = wav->getHeader();

	unsigned char *data = new unsigned char[header.subchunk2Size];
	for(long i = 0; i < header.subchunk2Size; i ++)
		if(i % 2 == 0)data[i] = 27;
		else data[i] = -27;

	newWav->setHeader(header);
	newWav->setData(data);
	return newWav;
}
Wav* applyFunc(Wav *wav)
{
	Wav *newWav = new Wav();
	WavHeader header = wav->getHeader();
	newWav->setLength(wav->getLength());
	header.chunkSize = 20 + newWav->getLength()  + header.subchunk1Size;
	unsigned char *data = new unsigned char[newWav->getLength()];
	for(long i = 0; i < newWav->getLength(); i ++)
	{
		data[i] = (0-(i % 2))*120 * ((float)i / newWav->getLength());
		if (i % 30000 < 15000) data[i] = -126 +(i % 2);
	}
	newWav->setHeader(header);
	newWav->setData(data);
	return newWav;
}
void checkDevice()
{
	int devCount;
	err( cudaGetDeviceCount(&devCount));
	if (devCount == 0) 
	{
		std::cout<<"no device\n";
		throw 0;
	}
}
void scaleOnGPU(char * fileName)
{
	try
	{
		float time = 0.0f;
		cudaEvent_t start, stop;
		checkDevice();	
		Wav *wav_in = new Wav();
		wav_in->readWav(fileName);

		err(cudaEventCreate(&start));
		err(cudaEventCreate(&stop));

		err(cudaEventRecord(start,0));
		Wav *scaletdola075 = TD_OLAScale_0_75_GPU(wav_in);
		err(cudaEventRecord(stop,0));
		err(cudaEventSynchronize(stop));
		err(cudaEventElapsedTime(&time,start, stop));
		std::cout<<"GPU 0.75 td-ola time: "<< time <<" \n";
		scaletdola075->writeWav("ScaleTDOLA075.wav");

		err(cudaEventRecord(start,0));
		Wav *scaletdola15 = TD_OLAScale_1_5_GPU(wav_in);
		err(cudaEventRecord(stop,0));
        err(cudaEventSynchronize(stop));
        err(cudaEventElapsedTime(&time,start, stop));
        std::cout<<"GPU 1.5 td-ola time: "<< time <<" \n";
		scaletdola15->writeWav("ScaleTDOLA150.wav");		

        err(cudaEventRecord(start,0));
        Wav *scaleLinear075 = interpolationScale_0_75_GPU(wav_in);
        err(cudaEventRecord(stop,0));
        err(cudaEventSynchronize(stop));
        err(cudaEventElapsedTime(&time,start, stop));
        std::cout<<"GPU 0.75 interpolation time: "<< time <<" \n";
		scaleLinear075->writeWav("ScaleLinear075.wav");

        err(cudaEventRecord(start,0));
        Wav *scaleLinear150 = interpolationScale_1_5_GPU(wav_in);
        err(cudaEventRecord(stop,0));
        err(cudaEventSynchronize(stop));
        err(cudaEventElapsedTime(&time,start, stop));
        std::cout<<"GPU 1.5 interpolation time: "<< time <<" \n";
		scaleLinear150->writeWav("ScaleLinear150.wav");
	}
	catch(...)
	{
		std::cout<<"scaling on GPU failed";
	}
}
void scaleOnCPU(char * fileName )
{
	try
	{
		float time = 0.0f;
        cudaEvent_t start, stop;

        err(cudaEventCreate(&start));
        err(cudaEventCreate(&stop));

		Wav *wav_in = new Wav();
		wav_in->readWav(fileName);

		err(cudaEventRecord(start,0));

		Wav *scaledinterp1_5wav = interpolationScale_1_5_CPU(wav_in);
		err(cudaEventRecord(stop,0));
        err(cudaEventSynchronize(stop));
        err(cudaEventElapsedTime(&time,start, stop));
        std::cout<<"CPU 1.5 interpolation time: "<< time <<" \n";


		err(cudaEventRecord(start,0));

		//scaledinterp1_5wav->toConsole();
		//scaledinterp1_5wav->writeWav("wav15interp.wav");
		Wav *scaledinterp0_75wav = interpolationScale_0_75_CPU(wav_in);
		err(cudaEventRecord(stop,0));
        err(cudaEventSynchronize(stop));
        err(cudaEventElapsedTime(&time,start, stop));
        std::cout<<"CPU 0.75 interpolation time: "<< time <<" \n";

		//scaledinterp0_75wav->writeWav("wav075interp.wav");


        err(cudaEventRecord(start,0));

		Wav *tdola1_5 = TD_OLAScale_1_5_CPU(wav_in);
		err(cudaEventRecord(stop,0));
        err(cudaEventSynchronize(stop));
        err(cudaEventElapsedTime(&time,start, stop));
        std::cout<<"CPU 1.5 td-ola time: "<< time <<" \n";

		//tdola1_5->writeWav("tdola15.wav");

		err(cudaEventRecord(start,0));


		Wav *tdola0_75 = TD_OLAScale_0_75_CPU(wav_in);
		err(cudaEventRecord(stop,0));
        err(cudaEventSynchronize(stop));
        err(cudaEventElapsedTime(&time,start, stop));
        std::cout<<"CPU 0.75 td-ola time: "<< time <<" \n";

		//tdola0_75->writeWav("tdola075.wav");

	}
	catch(...)
	{
		std::cout<<"scaling failed";
	}
}
int main(int argc, char* argv[])
{
	if (argc != 2)
	{
		std::cout<<"filename unspecified\n";
		return 0;
	}
	char *fileName = argv[1];
	std::cout<<"reading from " <<fileName<<"\n";
	scaleOnGPU(fileName);
	scaleOnCPU(fileName);	
	return 0;
}
